home *** CD-ROM | disk | FTP | other *** search
/ Almathera Ten Pack 3: CDPD 3 / Almathera Ten on Ten - Disc 3: CDPD3.iso / fish / 001-100 / 001-025 / 020 / multitasking / tutorial < prev    next >
Text File  |  1995-03-17  |  24KB  |  607 lines

  1.                         Multitasking on The Amiga
  2.  
  3.  
  4.         This tutorial was written by Leo L. Schwab, and was originally
  5. posted on the Programmer's Network and Amiga Conference on the WELL.
  6. Permission is hereby granted to freely distribute this tutorial provided
  7. credit is given to the original author (I want to be famous, you see :-).
  8.  
  9.  
  10. Foreword
  11. --------
  12.  
  13.         John Draper suggested that one way to become well-known is to write
  14. knowledgeable tutorials to help fellow programmers understand the system.
  15. This, then, is my attempt to tell people how to deal with basic multitasking
  16. on the Amiga.
  17.  
  18.         This tutorial will describe multitasking on the Exec level, not the
  19. DOS level.  As a personal preference, I prefer to avoid the DOS whenever I
  20. can and go straight to the Exec.  This does not cause undue difficulty, and
  21. in fact reduces overhead and headaches.
  22.  
  23.         This tutorial will deal mostly on the practical level, and will
  24. assume you know the basic concepts behind a multitasking environment.  Thus,
  25. this tutorial will use a cookbook approach to help you through getting your
  26. multiple tasks running and talking to each other.
  27.  
  28.         So get out your favorite C compiler and follow me.....
  29.  
  30.  
  31. Introduction
  32. ------------
  33.  
  34.         The first thing you should do when approacing the Amiga exec is to
  35. forget everything you know about any other kind of multitasking operating
  36. system you're familiar with.  Put it out of your mind.  Ignore it.
  37. Particularly UNIX.
  38.  
  39.         I say this because trying to use your knowledge of your favorite
  40. multitasking operating system is not going to help you much when you start
  41. dealing with the Amiga exec.  The exec is far and away vastly different from
  42. just about every other multitasking environment, with the possible exception
  43. of XINU (I mention XINU because there's a good book out on it, and many of
  44. the concepts in that book can be applied to the Amiga exec).
  45.  
  46.         The exec is, in my view, a bare-bones multitasking environment.
  47. There is just enough in the exec to do everything you need, although it may
  48. not do everything you want.  It won't automagically deallocate resources
  49. you've opened, and it's handling of runaway programs is not what many
  50. programmers would like.
  51.  
  52.         By contrast, however, the exec is small and compact.  It's written
  53. in extremely tight machine code.  Because it's small and quick, and doesn't
  54. try to "do things behind your back," it is a real-time operating system, and
  55. can respond to interrupts and signals rather efficiently.
  56.  
  57.         So, in exchange for having to do everything yourself, you get a very
  58. efficient multitasking executive.
  59.  
  60.  
  61. Getting Started
  62. ---------------
  63.  
  64.         A task, in the exec's eyes, consists of two parts:  A program
  65. segment residing somewhere in memory, and a task control block which is
  66. part of a linked list of tasks managed by the exec.
  67.  
  68.         The program segment can be anything and anywhere.  One particular
  69. way of getting a program segment might be to declare a function in C:
  70.  
  71. -----
  72. subtask ()
  73. {
  74.         ...
  75. }
  76. -----
  77.  
  78.         The task control block is an object that describes your task to the
  79. exec.  In particular, where your stack is in memory, where the stack pointer
  80. is, where the program counter should be, what signals your task has
  81. received, which signals it's waiting on, etc.  Every task must have a task
  82. control block.
  83.  
  84.         In addition, all tasks require a stack.  Even if you're not doing
  85. any subroutine calls or have any local variables, you are still required to
  86. have a stack so the exec can save your registers and return address if it
  87. should perform a context switch.  The absolute minimum size your stack can
  88. be is 70 bytes (q.v. RKM vol 1, p. 1-17,1-18).  A nice round number for a
  89. stack size is 1K.  Personally, I prefer to specify a stack of 2K just to be
  90. sure I have enough space for my variables and any subroutines I may call.
  91. The stack can be allocated out of the free memory pool using AllocMem().
  92. It is recommended (but not neccesary) that you not declare the memory public
  93. i.e. don't specify the MEMF_PUBLIC flag when calling AllocMem().
  94.  
  95.  
  96. Calling A Task Into Existence
  97. -----------------------------
  98.  
  99.         The exec support library provides a rather nice function for the
  100. purpose of creating tasks, called (oddly enough) CreateTask().
  101.  
  102.         CreateTask() performs only the most basic of task initialization
  103. steps.  It first allocates a chunk of memory for both the stack and task
  104. control block.  It then initializes the tc_SP{Upper,Lower,Reg} fields in the
  105. structure, and the tc_Node.ln_{Pri,Type,Name} entries in the node structure.
  106. Finally, it calls AddTask() to add your task to the system, and returns you
  107. a pointer to the task control block.  If anything goes wrong during this
  108. Process, it returns a null, and doesn't allocate anything.
  109.  
  110.         The calling format for CreateTask() is as follows:
  111.  
  112. struct Task *CreateTask (name, pri, initPC, stacksize)
  113. char *name;
  114. UBYTE pri;
  115. APTR initPC;
  116. ULONG stacksize;
  117.  
  118.         "name" is a pointer to a string that is the name of your task.  This
  119. is used if another task wants to find your task.
  120.  
  121.         "pri" is your task's priority.  This is a signed byte from -128 to
  122. +127.  0 is the canonical priority for new tasks.
  123.  
  124.         "initPC" is the starting value for your program counter.  Generally,
  125. this would be a pointer to a function, or some such thing.
  126.  
  127.         "stacksize" is how big a chunk of memory you want allocated for your
  128. stack and task control block combined.  CreateTask() allocates (stacksize)
  129. bytes of memory, then uses the first (sizeof (struct Task)) bytes for the
  130. task control block, and the rest is used for the actual stack.  This is
  131. important to remember if you want a specific stack size.
  132.  
  133.         The source code to CreateTask() can be found in the RKM vol. 2 a few
  134. pages past page E-78, in Appendix F.
  135.  
  136.         A typical call to CreateTask might look like this:
  137.  
  138. -----
  139. struct Task *tsk;
  140. extern void function();
  141. if (!(tsk = CreateTask ("Foo", 0, function, 2048)))
  142.         printf ("Couldn't create task\n");
  143. -----
  144.  
  145.         Note that CreateTask does only the basic initialization of the task
  146. control block.  If you are doing anything special, like exception
  147. processing, or interrupts, you can't use CreateTask; you'll have to cook up
  148. your own.  The source code in the RKM provides a good template for writing
  149. your own custom task creator should you need one.
  150.  
  151.  
  152. Getting Rid Of Tasks
  153. --------------------
  154.  
  155.         If you spawn a subtask, you'll probably want to kill it off
  156. eventually.  This is a bit tricky, but not too terrible.
  157.  
  158.         Firstly, if your subtask allocated any resources, it must be certain
  159. to close them before you try to remove it.  Remember, the exec does not
  160. perform resource management (at least not to the same level as in other
  161. operating systems); you have to free up everything yourself.  This could
  162. probably be accomplished by sending your subtask a message asking it to kill
  163. itself.  Once it gets this, it goes about closing all the stuff it may have
  164. opened before "exiting."
  165.  
  166.         If you use CreateTask(), you can use the complementary function
  167. DeleteTask() to get rid of a task you've spawned.  DeleteTask() performs a
  168. RemTask() on the task in question, then frees up the memory it allocated
  169. earlier for the stack and task control block.  A typical call to
  170. DeleteTask() might look like this:
  171.  
  172. -----
  173. struct Task *tsk;
  174. DeleteTask (tsk);
  175. -----
  176.  
  177.         "tsk" is the pointer to the task control block you got when you
  178. called CreateTask().
  179.  
  180.         Now pay attention, because this next bit is important.  If you
  181. intend to use DeleteTask() on a task to get rid of it, *you must never let
  182. that task exit on its own*.
  183.  
  184.         For example, if you have declared a function to be treated as a
  185. task, you must *never* let the function return.
  186.  
  187.         The reason for this is because of the action taken by the exec when
  188. a task exhausts its stack (that is, has exited on its own).  Unless you've
  189. specified a special clean-up function when you called AddTask() (which you
  190. can't do with CreateTask()), the default action taken by the exec is to
  191. remove the task from the system task list, by calling RemTask().
  192.  
  193.         DeleteTask() also calls RemTask().  Calling RemTask() on an already
  194. removed task is deadly.  If you try and do this, the system may or may not
  195. crash.  If it does crash, you may or may not get Guru Meditation.  If you do
  196. get Guru Meditation, it will probably be for something completely unrelated,
  197. most likely a memory problem.  If, on the other hand, the system doesn't
  198. crash, it may crash later when you try to start a different program.
  199. RemTask()ing removed tasks is a sure way to make your life miserable.
  200.  
  201.         There is, however, nothing wrong with calling RemTask() on a task
  202. that is currently active.  If you get the CPU, you can call RemTask() on any
  203. task in the system, and it will stop running and never be called again.
  204. Once you've RemTask()ed it, it's up to you to deallocate its stack and
  205. resources.
  206.  
  207.         Since it's a good idea to let tasks deallocate their own resources
  208. (except their stack, that's up to the task doing the RemTask()ing), a
  209. typical pseudo-code sequence might look like this:
  210.  
  211. -----
  212. if (RECEIVED_KILL_MESSAGE) {
  213.         deallocate_resources ();
  214.         return;
  215. }
  216. -----
  217.  
  218.         But we've already said that we can't let the task exit on it's own.
  219. So the "obvious" approach would be to do this:
  220.  
  221. -----
  222. if (RECEIVED_KILL_MESSAGE) {
  223.         deallocate_resources ();
  224.         while (1)
  225.                 ;
  226. }
  227. -----
  228.  
  229.         This is also deadly.  This creates a condition known as
  230. busy-waiting, and it's something to be avoided on the Amiga.  Suppose a low
  231. priority task wanted to kill off a high-priority task by sending it a
  232. message.  If the high-priority task entered the busy-waiting loop outlined
  233. above, the low-priority task would never get the CPU back (Remember, exec
  234. context switching is not the same as UNIX.  Whoever has the highest priority
  235. is the task currently running, regardless of how big a CPU hog it is).
  236. Somehow, we've got to get the task to enter a wait state of some kind.  This
  237. is the way I like to do it:
  238.  
  239. -----
  240. if (RECEIVED_KILL_MESSAGE) {
  241.         deallocate_resources ();
  242.         Wait (0);       /*  Wait for Godot  :-)  */
  243. }
  244. -----
  245.  
  246.         When you call Wait(), you specify a mask which is the logical OR of
  247. the signal bits you want to wake up on.  By specifying a mask of zero, no
  248. signal condition will satisfy the mask.  So the task goes to sleep and never
  249. wakes up again.  But the task is still in the system task list.  So at this
  250. point it is safe to call DeleteTask().
  251.  
  252.  
  253. Message Passing
  254. ---------------
  255.  
  256.         You might think message passing is a concept seperate from
  257. multitasking.  But multitasking and message passing are so closely
  258. intertwined in the Amiga exec that it would be incomplete to discuss one and
  259. not the other.  As was discussed above, message passing is an effective way
  260. to tell a task to go away.  So it seems appropriate to discuss it.
  261.  
  262.         To do message passing, you need two things:  A message port, and a
  263. message.
  264.  
  265.         A message is simply a chunk of memory with data in it.  The first
  266. part of the chunk is a node structure.  This is used to link messages in a
  267. FIFO queue.  The rest of the memory generally contains a pointer to the
  268. reply port, and the size of the message.
  269.  
  270.         A message port is a list header that the exec links messages on to.
  271. The message port also contains other information, such as which task it
  272. belongs to, which signal bit to assert when a new message arrives, and some
  273. flags.
  274.  
  275.         The structure definition for messages and message ports can be found
  276. in the RKM vol. 2 p. D-27.
  277.  
  278.  
  279. Making Message Ports
  280. --------------------
  281.  
  282.         The exec support library once again provides us with a convenient
  283. port creation function, called CreatePort().
  284.  
  285.         CreatePort() first allocates a signal bit (using AllocSignal()),
  286. then allocates memory for the port itself.  It then initializes the node
  287. structure, and the mp_{Flags,SigBit,SigTask} fields.  Finally, depending on
  288. whether or not you gave a name to the port, it either calls AddPort() (if
  289. you gave it a name), or simply calls NewList() for the message list
  290. structure (if you didn't give it a name).  The reason for this dual action
  291. is in case you want to create a truly private message port.  If you don't
  292. call AddPort(), other tasks will not be able to find your port by calling
  293. FindPort().  This is useful if you're sure other parts of the system will
  294. confuse your port with someone else's, or you simply don't want to receive
  295. messages from other tasks that may try to interrogate you (junk mail?).
  296.  
  297.         The calling convention for CreatePort() is as follows:
  298.  
  299. struct MsgPort *CreatePort (name, pri);
  300. char *name;
  301. BYTE pri;
  302.  
  303.         "name" is a pointer to a null-terminated string that is the name of
  304. your port.  This is so that other tasks can use FindPort() to get a pointer
  305. to your port.
  306.  
  307.         "pri" is a priority.  I asked one of the Amiga people what use
  308. priority on a message port was.  He explained that, since exec keeps
  309. everything in lists sorted according to priority, "Why not ports, too?"
  310. This does have a marginally practical value.  Since ports are sorted
  311. according to priority, when you call FindPort() on a port that has a high
  312. priority, it will still find the port, but it will find it quicker (since
  313. it's closer to the head of the list).  As a rule, I suggest assigning ports
  314. the same priority as the task owning it.
  315.  
  316.  
  317. Making Messages
  318. ---------------
  319.  
  320.         All you need to make a message is a chunk of memory, so go get one
  321. (I'll wait....).
  322.  
  323.         Got it?  Now put a message structure at the front of the block you
  324. allocated.  After that, put anything you need.  A typical way of creating a
  325. message might be like this:
  326.  
  327. -----
  328. struct InformationPacket {
  329.         struct Message message;
  330.         << insert relevant data declarations here >>
  331. } pack;
  332. -----
  333.  
  334.         Remember, a message can contain any kind of data you like.  The one
  335. thing exec expects to see is a message structure at the front.  It must be
  336. an instance of a message, not a pointer to one.  Beyond that requirement,
  337. exec doesn't care.
  338.  
  339.         One field of particular importance in the message structure is the
  340. mn_ReplyPort, which will be discussed later.
  341.  
  342.         As an illustration, take a look at the IORequest structures (RKM
  343. vol. 2 p. D-23).  See what the first thing is in each of them?  A message
  344. structure.
  345.  
  346.  
  347. Sending Messages
  348. ----------------
  349.  
  350.         In order to send a message, you must have a valid pointer to a
  351. message port, and a pointer to a message.  When you have these, you pass
  352. them to the exec function PutMsg().  The calling convention is as follows:
  353.  
  354. PutMsg (port, msg);
  355. struct MsgPort *port;
  356. struct Message *msg;
  357.  
  358.         "port" is a pointer to a message port that was obtained either by
  359. calling CreatePort(), or (more likely) by calling FindPort().
  360.  
  361.         "msg" is a pointer to a message you've constructed, as outlined
  362. above.
  363.  
  364.         Now pay attention, because this is another important bit.  PutMsg()
  365. does not copy the message you're passing, but simply assigns the right to
  366. use the memory to the task owning the port you're sending the message to.
  367.  
  368.         Let me rephrase that.  PutMsg() does not make a copy of your message
  369. and pass the copy to the port you're sending to.  It gives the original
  370. message to the port.
  371.  
  372.         Let's say you've allocated a block of RAM and are using it as a
  373. message.  Then you call PutMsg(), passing a pointer to your block of RAM.
  374. You now no longer own that block of RAM; you have passed ownership (and
  375. therefore the right to use and modify the RAM) to the port you sent it to.
  376.  
  377.         This means that, once you've passed ownership to the receiving task,
  378. you are *not* allowed to modify the message.  If you *really* know what
  379. you're doing, you can still access the contents of the message, but this can
  380. get you into trouble, as will be illustrated later.
  381.  
  382.  
  383. Receiving Messages
  384. ------------------
  385.  
  386.         To receive a message, you must have a properly initialized message
  387. port (generally made with CreatePort()).  If you have properly initialized
  388. the mp_{SigTask,SigBit} fields of the message port structure (CreatePort()
  389. does this for you), you will receive a signal when a message arrives at
  390. your port.
  391.  
  392.         There are several ways to see if a message has arrived at your port.
  393. The most basic of these is the exec function GetMsg().  It works like this:
  394.  
  395. msg = GetMsg (port);
  396. struct Message *msg;
  397. struct MsgPort *port;
  398.  
  399.         "msg" is a pointer to a block of RAM being used as a message.  The
  400. task receiving the message should "know" how the RAM is structured so it can
  401. read the data properly.  If a message is waiting, GetMsg() returns a pointer
  402. to it and removes the message from the port.  If there are no messages on
  403. your port, GetMsg() returns a null.
  404.  
  405.         "port" is a pointer to your message port that you are expecting
  406. messages to arrive on.
  407.  
  408.         If you want your task to wait for a message to arrive before you do
  409. anything else, you might try to do it like this:
  410.  
  411. -----
  412. while (!(msg = GetMsg (port)))
  413.         ;
  414. -----
  415.  
  416.         This is the very deadly busy-waiting loop again, and you should
  417. avoid it like the plague.  The proper way to wait for a message is like
  418. this:
  419.  
  420. -----
  421. msg = WaitPort (port);
  422. GetMsg (port);
  423. -----
  424.  
  425.         WaitPort() first checks to see if you have any messages, and if you
  426. don't, goes to sleep until one arrives.  Note that, although WaitPort() does
  427. return a pointer to a message, it does not remove it from the port.  You
  428. should always follow WaitPort() with a call to GetMsg() to dequeue the
  429. message.
  430.  
  431.         Another way to wait for messages is to use exec's general-purpose
  432. Wait() function.  It's used like this (when waiting for messages):
  433.  
  434. -----
  435. Wait (1 << port -> mp_SigBit);
  436. msg = GetMsg (port);
  437. -----
  438.  
  439.         Note that Wait() does not "buffer" i.e. if two messages arrive at
  440. your port, Wait() will not report both of them.  If you're using Wait() to
  441. wait for messages, the proper way to make sure you get all of them is like
  442. this:
  443.  
  444. -----
  445. Wait (1 << port -> mp_SigBit);
  446. while (msg = GetMsg (port)) {
  447.         << relevant message handling here >>
  448. }
  449. -----
  450.  
  451.         It may look like busy-waiting, but it isn't.  The while-loop
  452. continues to execute until all the messages on your port have been received
  453. and dequeued.  Once you've gotten them all, the loop exits and you're free
  454. to check on other things.
  455.  
  456.         When you have gotten the message, you are free to access and modify
  457. the message as much as you like.  After all, it's now your RAM.
  458.  
  459.         After you are through using a message, it is a good idea to reply to
  460. it.  Tasks that send you messages often wait for you to reply to them so
  461. they can get on with their work.  This is done with the exec function
  462. ReplyMsg(), which works like this:
  463.  
  464. ReplyMsg (msg);
  465. struct Message *msg;
  466.  
  467.         "msg" is a pointer to a message that arrived at your port.
  468.  
  469.         Note that the message's mn_ReplyPort field must be valid for
  470. ReplyMsg() to work properly.  Once you have replied to a message, you are no
  471. longer allowed to use the data in the message, since you have returned
  472. ownership to the task that sent you the message.
  473.  
  474.         Note also that, when you reply to a message, it is exactly as if you
  475. said:
  476.  
  477. -----
  478. PutMsg (msg -> mn_ReplyPort, msg);
  479. -----
  480.  
  481.         This means that the task receiving the reply will receive it as
  482. though it were an ordinary message.  The receiving task should know what to
  483. do with replies to messages.  It is not a good idea to reply to a reply.
  484. This is a typical way of sending a message and waiting for a reply:
  485.  
  486. -----
  487. PutMsg (sendport, msg);
  488. WaitPort (replyport);
  489. GetMsg (replyport);     /*  We don't reply to replies, we just get them  */
  490. -----
  491.  
  492.  
  493. Traps To Avoid
  494. --------------
  495.  
  496.         One trap that is easy to fall into is failing to remember that, once
  497. you reply a message, you can no longer rely on the information in the
  498. message.  Here's one incantation of this trap I fell into.
  499.  
  500. -----Low priority task segment-----
  501.         struct IORequest *msg;
  502.         struct MsgPort *port;
  503.  
  504.         /*  msg initialized elsewhere (possibly with CreateExtIO())  */
  505.         port = CreatePort ("foo port", 0);
  506.         if (msg = GetMsg (port)) {
  507.                 ReplyMsg (msg);
  508.                 if (msg -> io_Command == SPECIALVAL)
  509.                         exit (-1);
  510.         }
  511. -----High priority task segment-----
  512.         struct IORequest *msg;
  513.         struct MsgPort *sendport, *replyport;
  514.  
  515.         replyport = CreatePort ("replies", 1);
  516.         sendport = FindPort ("foo port");
  517.         if (SPECIAL_CASE) {
  518.                 msg -> io_Command = SPECIALVAL;
  519.                 PutMsg (sendport, msg);
  520.                 WaitPort (replyport);
  521.                 GetMsg (replyport);
  522.         }
  523.         msg -> io_Command = NORMAL_VAL;
  524. -----
  525.  
  526.         Here's what happens.  The high priority task detects a special case,
  527. and sends a message to the low priority task with a special value in the
  528. command field.  Then it goes to sleep, waiting for a reply.
  529.  
  530.         The low priority task wakes up with a message in its port.  It gets
  531. the message, then replies to it right away so the high priority task won't
  532. have to wait too long.
  533.  
  534.         The moment the low priority task replies, the high priority task
  535. wakes up again (since the WaitPort() just got satisfied), gets the message,
  536. and changes the command field back to its normal value.
  537.  
  538.         Eventually down the line, the high priority task goes to sleep on
  539. something, and the low priority task starts where it left off.  Remember
  540. that the low priority task had just replied the message and was about the
  541. check the command field for SPECIALVAL.  But it lost control of the CPU when
  542. it replied the message, and the command field got changed back to
  543. NORMAL_VAL.  So the test for SPECIALVAL will fail, and the exit() function
  544. will never get called.
  545.  
  546.         The way to avoid this trap is to copy *all* the data you intend to
  547. examine into your own private storage before you reply the message.  The fix
  548. to the above code would be this:
  549.  
  550. -----Low priority task segment-----
  551.         int cmd;
  552.  
  553.         if (msg = GetMsg (port)) {
  554.                 cmd = msg -> io_Command;
  555.                 ReplyMsg (msg);
  556.                 if (cmd == SPECIALVAL)
  557.                         exit (-1);
  558.         }
  559. -----
  560.  
  561.         If you intend to modify the message before you reply to it, you
  562. should do all modification before replying the message.
  563.  
  564.         Remember: when you reply a message, you give up all rights to use
  565. the data in the message.
  566.  
  567.  
  568. An Example
  569. ----------
  570.  
  571.         Attached (somewhere) is a piece of source code (in C) that
  572. illustrates the creation of a task, creation of message ports, message
  573. passing, passing and modifying data in the message, and cleaning up.  It
  574. compiles sucessfully under Lattice 3.03 (sorry, I couldn't make it work with
  575. Manx).  Ignore the compiler warnings about improperly typed pointers.
  576.  
  577.         Note: this program, once compiled and linked (remember the "faster"
  578. argument!), __s_e_e_m_s_ to work OK, provided the program is on
  579. disk.  If you try to run it out of RAMdisk, it will run, but the next
  580. program you try to run crashes the machine.  I'm not sure why this is
  581. happening (I suspect I'm forgetting to do something terribly important), and
  582. would appreciate help in this area.  Other than that, it works.
  583.  
  584.  
  585. Bibliography
  586. ------------
  587.  
  588.         ROM Kernel Manual (v1.1), Volume 1
  589.         pp. 1-11 - 1-21, 1-29 - 1-37
  590.  
  591.         ROM Kernel Manual (v1.1), Volume 2
  592.         pp. A-64, A-65, A-68, A-74, A-75, A-78, A-82, A-85 - A-87, A-93,
  593.             A-94, D-23, D-27, D-29, D-30, Appendix F (past page E-78)
  594.  
  595. ----------------
  596.  
  597.         I hope you've found this helpful.  If you have any suggestions or
  598. corrections, or just want to flame me, feel free to leave me some mail.
  599.  
  600.                                         Have fun,
  601.                                         Leo L. Schwab
  602. --------
  603. ...!{hplabs,dual,well}!unicom!schwab    (or)    ...!well!ewhac
  604.         "We interrupt this program to annoy you, and to make things
  605. generally irritating for you."
  606.  
  607.